package com.tuu.utils;
import com.tuu.configBean.RedisUtil;
import com.tuu.constvar.Constants;
import com.tuu.exception.BizException;
import com.tuu.pojo.JwtClainsObject;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * API调用认证工具类，采用RSA加密
 */
@Component  //redis是静态类 需要加这个
public class JWTUtils {
    /**
     *
     * @throws Exception
     */
    @Autowired
    private static RedisUtil redisUtil;
    @Value("${jwt.secretKey}")
    private static String secretKey;
    //参考redis注入
    //https://blog.csdn.net/kung_com/article/details/98082372
    @Autowired
    public void setRedisService(RedisUtil redisUtil) {
        JWTUtils.redisUtil = redisUtil;
    }
    public static String createJWT(JwtClainsObject jwtObject) throws Exception {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        long nowMillis = System.currentTimeMillis();//生成JWT的时间
        Date now = new Date(nowMillis);
        Map<String,Object> claims = new HashMap<String,Object>();//创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        claims.put("uid", jwtObject.getSubjectUid());
        claims.put("user_name", jwtObject.getUsername());
        claims.put("ip_address", jwtObject.getIp());
        SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的，一般可以从本地配置文件中读取，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        //下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder，设置jwt的body
                .setClaims(claims)          //如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setId(jwtObject.getRandomId())                  //设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
                .setIssuedAt(now)           //iat: jwt的签发时间
                .setSubject(jwtObject.getSubjectUid())        //sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
                .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
        if (jwtObject.getTtlMillis() >= 0) {
            long expMillis = nowMillis + jwtObject.getTtlMillis();
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);     //设置过期时间
        }
        return builder.compact();           //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
        //打印了一哈哈确实是下面的这个样子
        //eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiIiLCJ1c2VyX25hbWUiOiJhZG1pbiIsIm5pY2tfbmFtZSI6IkRBU0RBMTIxIiwiZXhwIjoxNTE3ODI4MDE4LCJpYXQiOjE1MTc4Mjc5NTgsImp0aSI6Imp3dCJ9.xjIvBbdPbEMBMurmwW6IzBkS3MPwicbqQa2Y5hjHSyo
    }
    /**
     * 解密jwt
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception{
        SecretKey key = generalKey();  //签名秘钥，和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)         //设置签名的秘钥
                .parseClaimsJws(jwt).getBody();//设置需要解析的jwt
        System.out.println(claims.get("user_name"));
        return claims;
    }
    public static Map<String,Object> checkJWT(String jwt,HttpServletRequest request) throws Exception{
        SecretKey key = generalKey();  //签名秘钥，和生成的签名的秘钥一模一样
        //设置需要解析的jwt
        Date exp=null;
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss");
        //long nowMillis = System.currentTimeMillis();
        String ip=IPAdressUtils.getIpAddress(request);
        System.out.println("ipadress:"+ip);
        BrowerUtils bu=BrowerUtils.getBrowerInfo(request);
        Map<String,Object> map=new HashMap<>();
        Claims claims=null;
        try {
            claims = parseJWT(jwt);
            exp=claims.getExpiration();
            System.out.println("过期时间"+sdf.format(claims.getExpiration()));
            if(!ip.equals(claims.get("ip_address"))){
                System.out.println("ip地址不正确");
                throw  new BizException("-1","ip识别错误，请重新登陆");
            }
            String browerCont=bu.getBrowserName()+bu.getOperatingSystem()+bu.getBrowserVersion();
            if(!browerCont.equals(claims.get("brower_contact"))){
                System.out.println("brower_contact不正确");
                throw  new BizException("-1","设备识别错误，请重新登陆");
            }
            //GlobalMap.set("user_name",claims.get("user_name"));
        }catch (ExpiredJwtException e){
            System.out.println("抛出token过期错误");
            System.out.println("过期时间"+sdf.format(e.getClaims().getExpiration()));
            //e.getClaims().getSubject();
            //System.out.println("抛出错误---uid---"+e.getClaims().getSubject());
            //String[] dd=new String[]{"dfdf","dfdfd","787878"};
            //redisUtil.set("efef","ef89898989");
            //redisUtil.set("uu",dd);
            //System.out.println("efef---"+redisUtil.get("efef"));
            //String[] yy=(String[])redisUtil.get("uu");
            //System.out.println("uu---"+yy[2]);
            String uidContact="uid"+e.getClaims().getSubject()+"token";

            getRedis(uidContact,jwt);

        }catch (Exception e){
            throw  new BizException("-1","token错误，请重新登陆");
        }
        String uidContact="uid"+claims.getSubject()+"token";
        //String redisToken=(String) redisUtil.get(uidContact);
        //if(redisToken!=null && redisToken.equals(jwt)){
            //重新设置过期时间5分钟
            //redisUtil.expire(uidContact,300);
        //}
        getRedis(uidContact,jwt);
        //uid aes加密后的uid
        map.put("bii",MyAesUtils.encrypt(claims.getSubject(),Constants.JwtConst.AES16KEY.getValue()) );
        map.put("nickname",claims.get("user_name"));
        System.out.println("验证成功。。。返回cookies");
        return map;
    }
    public static boolean pureCheckJWT(String jwt,HttpServletRequest request) throws Exception{
        SecretKey key = generalKey();  //签名秘钥，和生成的签名的秘钥一模一样
        //设置需要解析的jwt
        Date exp=null;
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss");
        //long nowMillis = System.currentTimeMillis();
        String ip=IPAdressUtils.getIpAddress(request);
        System.out.println("ipadress:"+ip);
        BrowerUtils bu=BrowerUtils.getBrowerInfo(request);
        Map<String,Object> map=new HashMap<>();
        Claims claims=null;
        try {
            claims = parseJWT(jwt);
            exp=claims.getExpiration();
            System.out.println("过期时间"+sdf.format(claims.getExpiration()));
            if(!ip.equals(claims.get("ip_address"))){
                System.out.println("ip地址不正确");
                throw  new BizException("-1","token错误，请重新登陆");
            }
            String browerCont=bu.getBrowserName()+bu.getOperatingSystem()+bu.getBrowserVersion();
            if(!browerCont.equals(claims.get("brower_contact"))){
                System.out.println("brower_contact不正确");
                throw  new BizException("-1","token错误，请重新登陆");
            }
            //GlobalMap.set("user_name",claims.get("user_name"));
        }catch (ExpiredJwtException e){
            System.out.println("抛出token过期错误");
            System.out.println("过期时间"+sdf.format(e.getClaims().getExpiration()));
            //e.getClaims().getSubject();
            //System.out.println("抛出错误---uid---"+e.getClaims().getSubject());
            //String[] dd=new String[]{"dfdf","dfdfd","787878"};
            //redisUtil.set("efef","ef89898989");
            //redisUtil.set("uu",dd);
            //System.out.println("efef---"+redisUtil.get("efef"));
            //String[] yy=(String[])redisUtil.get("uu");
            //System.out.println("uu---"+yy[2]);
            String uidContact="uid"+e.getClaims().getSubject()+"token";
            getRedis(uidContact,jwt);
            System.out.println("token在redis未过期，已重新刷新token");
            return true;
        }catch (Exception e){
            throw  new BizException("400","token流程异常，请稍后重试");
        }
        String uidContact="uid"+claims.getSubject()+"token";
        getRedis(uidContact,jwt);
        System.out.println("验证成功。。。");
        return true;
    }
    public static boolean logoutJWT(String jwt,HttpServletRequest request) throws Exception{
        SecretKey key = generalKey();  //签名秘钥，和生成的签名的秘钥一模一样
        //设置需要解析的jwt
        Date exp=null;
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy‐MM‐dd hh:mm:ss");
        //long nowMillis = System.currentTimeMillis();
        String ip=IPAdressUtils.getIpAddress(request);
        System.out.println("ipadress:"+ip);
        BrowerUtils bu=BrowerUtils.getBrowerInfo(request);
        Map<String,Object> map=new HashMap<>();
        Claims claims=null;
        try {
            claims = parseJWT(jwt);
            exp=claims.getExpiration();
            System.out.println("过期时间"+sdf.format(claims.getExpiration()));
            if(!ip.equals(claims.get("ip_address"))){
                System.out.println("ip地址不正确");
                throw  new BizException("500","ip来源错误，请重新登陆");
            }
            String browerCont=bu.getBrowserName()+bu.getOperatingSystem()+bu.getBrowserVersion();
            if(!browerCont.equals(claims.get("brower_contact"))){
                System.out.println("brower_contact不正确");
                throw  new BizException("500","设备识别错误，请重新登陆");
            }
            //GlobalMap.set("user_name",claims.get("user_name"));
        }catch (ExpiredJwtException e){
            System.out.println("抛出token过期错误");
            System.out.println("过期时间"+sdf.format(e.getClaims().getExpiration()));

            String uidContact="uid"+e.getClaims().getSubject()+"token";
            getRedisTokenAndDel(uidContact);

            return true;
        }catch (BizException e){
            throw  new BizException(e.getErrorCode(),e.getErrorMsg());
        }
        catch (Exception e){
            throw  new BizException("400","token流程异常，请稍后重试");
        }
        String uidContact="uid"+claims.getSubject()+"token";
        getRedisTokenAndDel(uidContact);
        System.out.println("退出成功。。。");
        return true;
    }
    /**
     * 由字符串生成加密key
     * @return
     */
    public static SecretKey generalKey(){
        //String stringKey = Constant.JWT_SECRET;//本地配置文件中加密的密文7786df7fc3a34e26a61c034d5ec8245d
        //String stringKey = "8899";//本地配置文件中加密的密文7786df7fc3a34e26a61c034d5ec8245d
        byte[] encodedKey = Base64.decodeBase64(secretKey);//本地的密码解码[B@152f6e2
        System.out.println("密钥是"+secretKey);
        System.out.println(encodedKey);//[B@152f6e2
        System.out.println(Base64.encodeBase64URLSafeString(encodedKey));//7786df7fc3a34e26a61c034d5ec8245d
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
        return key;
    }
    //token没过期 就重新刷新redis token的时间
    public static void getRedis(String uidContact,String jwt) throws Exception{
        try {
            String redisToken=(String) redisUtil.get(uidContact);
            if(redisToken!=null && redisToken.equals(jwt)){
                //重新设置过期时间5分钟
                redisUtil.expire(uidContact,Long.parseLong(""+Constants.JwtConst.EXPMILLIS.getTimee()/1000L));
            }else{
                System.out.println("token失效，请......");
                throw  new BizException("-1","token失效，请重新登陆");
            }
        }catch (BizException e){
            throw  new BizException(e.getErrorCode(),e.getErrorMsg());
        }
        catch (Exception e){
            throw  new BizException("500","缓存服务器错误，请稍后重试");
        }
    }
    //token没过期 就重新刷新redis token的时间
    public static void getRedisTokenAndDel(String uidContact) throws Exception{
        try {
            String redisToken=(String) redisUtil.get(uidContact);
            //redisToken.equals(jwt)
            if(StringUtils.isNotBlank(redisToken)){
                redisUtil.del(uidContact);
            }else{
                System.out.println("getRedisTokenAndDel token失效，请重新登陆...");
                throw  new BizException("-1","token失效，请重新登陆");
            }
        }catch (BizException e){
            throw  new BizException(e.getErrorCode(),e.getErrorMsg());
        }catch (Exception e){
            throw  new BizException("500","缓存服务器错误，请稍后重试");
        }
    }
}